﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace Dsl.Ped.Controls.ActionBorder
{
    /// <summary>
    /// This is the implementation of the <see cref="ActionBorderControl"/> which is 
    /// a control, that might be used as a border of a content. When the user moves the mouse
    /// over it, the button for a custom action appears together with the border. The button
    /// <see cref="ICommand"/> may be hooked to any user action.
    /// The hint for this issue (MouseDirectlyOver) was provided on the  
    /// http://blogs.msdn.com/b/mikehillberg/archive/2006/09/21/mytreeviewhelperismousedirectlyoveritem.aspx
    /// </summary>
    [TemplatePart(Name = "PART_Button", Type = typeof(Button))]
    [TemplatePart(Name = "PART_Border", Type = typeof(Button))]
    public class ActionBorderControl : ContentControl, ICommandSource
    {
        private static readonly DependencyPropertyKey IsSelectedKey;

        public static readonly DependencyProperty IsSelectedProperty;

        public static readonly DependencyProperty CommandProperty;

        public static readonly DependencyProperty CommandParameterProperty;

        public static readonly DependencyProperty CommandTargetProperty;

        public static readonly RoutedEvent SelectedEvent;

        private static ActionBorderControl currentActionBorderControl;

        private static readonly RoutedEvent UpdateSelectedEvent;

        /// <summary>
        /// Static constructor. Register Events and <see cref="DependencyProperty"/> of the 
        /// <see cref="ActionBorderControl"/>
        /// </summary>
        static ActionBorderControl()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(ActionBorderControl), new FrameworkPropertyMetadata(typeof(ActionBorderControl)));

            IsSelectedKey = DependencyProperty.RegisterAttachedReadOnly(
                "IsSelected",
                typeof(bool),
                typeof(ActionBorderControl),
                new FrameworkPropertyMetadata(
                    null,
                    CalculateIsSelected));

            IsSelectedProperty = IsSelectedKey.DependencyProperty;

            CommandProperty = DependencyProperty.Register(
                "Command",
                typeof(ICommand),
                typeof(ActionBorderControl),
                new FrameworkPropertyMetadata(null));

            CommandParameterProperty = DependencyProperty.Register(
                 "CommandParameter", 
                 typeof(object), 
                 typeof(ActionBorderControl), 
                 new FrameworkPropertyMetadata(null));

            CommandTargetProperty = DependencyProperty.Register(
                 "CommandTarget", 
                 typeof(IInputElement), 
                 typeof(ActionBorderControl), 
                 new FrameworkPropertyMetadata(null));

            UpdateSelectedEvent = EventManager.RegisterRoutedEvent(
                 "UpdateSelectedEvent", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ActionBorderControl));

            SelectedEvent = EventManager.RegisterRoutedEvent(
                "Selected", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ActionBorderControl));    

            EventManager.RegisterClassHandler(typeof(ActionBorderControl), UpdateSelectedEvent, new RoutedEventHandler(OnUpdateSelectedEvent));
            EventManager.RegisterClassHandler(typeof(ActionBorderControl), MouseMoveEvent, new RoutedEventHandler(OnMouseMoveEvent), false);
            EventManager.RegisterClassHandler(typeof(ActionBorderControl), MouseLeaveEvent, new RoutedEventHandler(OnMouseLeaveEvent), false);
        }

        /// <summary>
        /// Get the <see cref="IsSelectedProperty"/> value
        /// </summary>
        /// <param name="obj"><see cref="ActionBorderControl"/> that keeps the <see cref="IsSelectedProperty"/></param>
        /// <returns>True, if the <see cref="ActionBorderControl"/> is selected, False otherwise</returns>
        public static bool GetIsSelected(DependencyObject obj)
        {
            return (bool)obj.GetValue(IsSelectedProperty);
        }
        
        /// <summary>
        /// Gets or sets the <see cref="Button.Command"/> of the border
        /// button (PART_Button)
        /// </summary>
        public ICommand Command
        {
            get
            {
                return (ICommand)base.GetValue(CommandProperty);
            }
            set
            {
                base.SetValue(CommandProperty, value);
            }
        }
        
        /// <summary>
        /// Gets or sets the <see cref="Button.CommandParameter"/> of the border
        /// button (PART_Button)
        /// </summary>
        public object CommandParameter
        {
            get
            {
                return base.GetValue(CommandParameterProperty);
            }
            set
            {
                base.SetValue(CommandParameterProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets the <see cref="Button.CommandTarget"/> of the boder
        /// button (PART_Button)
        /// </summary>
        public IInputElement CommandTarget
        {
            get
            {
                return (IInputElement)base.GetValue(CommandTargetProperty);
            }
            set
            {
                base.SetValue(CommandTargetProperty, value);
            }
        }

        /// <summary>
        /// Event handler, gets executed when the Mouse appeares over the <see cref="ActionBorderControl"/>
        /// </summary>
        /// <param name="sender"><see cref="ActionBorderControl"/> that raise this event</param>
        /// <param name="args">Arguments of the <see cref="Control.MouseMoveEvent"/></param>
        protected static void OnMouseMoveEvent(object sender, RoutedEventArgs args)
        {
            OnMouseEvent(sender, args);
        }

        /// <summary>
        /// Event handler, gets executed when the Mouse leaves the <see cref="ActionBorderControl"/>
        /// </summary>
        /// <param name="sender"><see cref="ActionBorderControl"/> that raise this event</param>
        /// <param name="args">Arguments of the <see cref="Control.MouseLeaveEvent"/></param>
        protected static void OnMouseLeaveEvent(object sender, RoutedEventArgs args)
        {
            OnMouseEvent(sender, args);
        }

        /// <summary>
        /// Event handler for the <see cref="Control.MouseMoveEvent"/> and <see cref="Control.MouseLeaveEvent"/>.
        /// The handler gets executed when the mouse appears over the <see cref="ActionBorderControl"/> or leaves it.
        /// Invalidates the <see cref="IsSelectedProperty"/> accordingly
        /// </summary>
        /// <param name="sender"><see cref="ActionBorderControl"/> that raised this event</param>
        /// <param name="args">Arguments of the <see cref="Control.MouseMoveEvent"/> or <see cref="Control.MouseLeaveEvent"/></param>
        protected static void OnMouseEvent(object sender, RoutedEventArgs args)
        {
            lock (IsSelectedProperty)
            {
                if (currentActionBorderControl != null)
                {
                    DependencyObject oldItem = currentActionBorderControl;
                    currentActionBorderControl = null;
                    oldItem.InvalidateProperty(IsSelectedProperty);
                }

                var currentPosition = Mouse.DirectlyOver;

                // See if the mouse is still over something (any element, not just a tree view item).
                if (currentPosition != null)
                {
                    var newItemArgs = new RoutedEventArgs(UpdateSelectedEvent);
                    currentPosition.RaiseEvent(newItemArgs);
                }
            }
        }

        /// <summary>
        /// Event handler for the <see cref="UpdateSelectedEvent"/>. When it is received, the
        /// sender is the nearest parent (from the Mouse) in the tree hierarchy of the <see cref="Type"/> equal to <see cref="ActionBorderControl"/>.
        /// </summary>
        /// <param name="sender"><see cref="ActionBorderControl"/> that raised the event</param>
        /// <param name="args">Arguments of the <see cref="SelectedEvent"/></param>
        protected static void OnUpdateSelectedEvent(object sender, RoutedEventArgs args)
        {
            currentActionBorderControl = sender as ActionBorderControl;
            if (currentActionBorderControl == null)
            {
                return;
            }

            currentActionBorderControl.InvalidateProperty(IsSelectedProperty);
            args.Handled = true;
        }

        /// <summary>
        /// Calculate if the <see cref="ActionBorderControl"/> is selected.
        /// This is the coercian method for the <see cref="IsSelectedProperty"/> that
        /// gets executed when the <see cref="IsSelectedProperty"/> is calculated
        /// </summary>
        /// <param name="item">Selected item</param>
        /// <param name="value">Not used during the calculation</param>
        /// <returns>True, if the selected item equals <see cref="currentActionBorderControl"/></returns>
        private static object CalculateIsSelected(DependencyObject item, object value)
        {
            return Equals(item, currentActionBorderControl);
        }
    }
}
